﻿using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Reflection;

namespace System.Windows.Forms
{
	/// <summary>
	/// A listView interpreting of the dataGrid
	/// </summary>
	public class DataListView : System.Windows.Forms.ListView
	{
		#region Imported function
		// Thease functions are neaded for the item draw message to be grabed
		[DllImport("User32.dll",CharSet = CharSet.Auto)]
		private static extern long GetWindowLong(IntPtr hwnd, int nIndex);
		[DllImport("User32.dll",CharSet = CharSet.Auto)]
		private static extern long SetWindowLong(IntPtr hwnd, int nIndex, long dwNewLong);
		#endregion

		#region Private members 
		private object _dataSource;		// the datasource object
        private bool isList = false;
		private DataTable _dataTable;	// tha base (datatable)
		private string _dataMember;		// the datamemeber 
        private ArrayList _dataMemberForList;

        public ArrayList DataMemberForList
        {
            get { return _dataMemberForList; }
            set { _dataMemberForList = value; }
        }
		private bool _checkTableExists;	// determines if the table existence is checked at dataset and dataview
		private Color _alternatingBackColor;	// the alterning back color
		private DataRowChangeEventHandler onModify;	// handler to the table modifications
		private DataRowChangeEventHandler onDelete;	// handler to the table modifications
		#endregion

		#region Overriden properties
		// Full row select is new and read only
		public new bool FullRowSelect 
		{
			get 
			{
				return true;
			}
            set { }
		}
		// View is new read only
		public new View View 
		{
			get 
			{
				return View.Details;
			}
            set { }
		}
		#endregion

		/// <summary>
		/// Initializes a new object
		/// </summary>
		public DataListView()
		{
			base.FullRowSelect = true;
			base.View = View.Details;
			base.GridLines = false;

			this.SetStyle(ControlStyles.DoubleBuffer, true);	// enamble doublebuffering => smoothness ;)
	
            this.AlternatingBackColor = this.BackColor;
			this.MultiSelect = false;	// default single line select

			// catch the data source change
			this.onModify = new DataRowChangeEventHandler(table_RowChanged);
			this.onDelete = new DataRowChangeEventHandler(table_RowDeleted);
		}


		#region Added properties

			#region Designer
			/// <summary>
			/// Sets the lists data source
			/// </summary>
			// the IList and IListSource wore not implemented (they are supported by any object that has an datasource, except this :) )
			// it supports only ADO.NET's dataTable , dataSet + tableName, or dataView
			[System.ComponentModel.Browsable(true), System.ComponentModel.Category("Data"), System.ComponentModel.Description("The DataSource of the Grid")]
			public object DataSource 
			{
				get 
				{
					return _dataSource;
				}
				set 
				{
					_dataMember = string.Empty;	// clear the data member
					_dataSource = value;
					
					InitViewForNull();

					if(value == null) 
						return;	// nothing to do

					if(value.GetType() == typeof(DataSet)) 
					{
						if(_dataMember != string.Empty) 
						{
							InitViewForDataSet();
							return;
						}
					}
					else if(value.GetType() == typeof(DataTable)) 
					{
						InitViewForDataTable();
						return;
					}
					else if(value.GetType() == typeof(DataView)) 
					{
						InitViewForDataView();
						return;
					}
                    else if (value.GetType() == typeof(ArrayList))
                    {
                        InitViewFromAArrayList(this.DataMemberForList);
                        return;
                    }
					else 
					{
						bool throwException;

						throwException = false;
						try 
						{
							IList list = (IList) value;
							throwException = true;	
						} 
						catch {}
						if(throwException)
							throw new NotImplementedException("IList data sources are not implemented");


						throwException = false;
						try 
						{
							IListSource listSource = (IListSource) value;
							throwException = true;
							
						}
						catch {}
						if(throwException)
							throw new NotImplementedException("IListSource data sources are not implemented");
						

						throw new NotSupportedException("The object givven as a data source is not supported.");
					}
				}
			}

			/// <summary>
			/// Sets the lists data member
			/// </summary>
			[System.ComponentModel.Browsable(true), System.ComponentModel.Category("Data"), System.ComponentModel.Description("The DataMember of the Grid")]
			public string DataMember 
			{
				get 
				{
					return _dataMember;
				}
				set 
				{
					_dataMember = value;
					if(_dataSource == null) 
						return; // wait for the dataset and then the datamember
					if(_dataSource.GetType() == typeof(DataSet)) 
					{
						InitViewForDataSet();
						return;
					}
					else 
						throw new NotSupportedException("The object givven as a data source does not have multiple members.");
				}
			}
			/// <summary>
			/// Determines if an exception is throwed when a null table is supplied as a data member or a dataview with no table
			/// </summary>
			[System.ComponentModel.Browsable(true), System.ComponentModel.Category("Data"), System.ComponentModel.Description("Determines if an exception is throwed when a null table is supplied as a data member or a dataview with no table")]
			public bool CheckTableExists 
			{
				get 
				{
					return _checkTableExists;
				}
				set 
				{
					_checkTableExists = value;
				}
			}
			/// <summary>
			/// The alternating back color
			/// </summary>
			[System.ComponentModel.Browsable(true), System.ComponentModel.Category("Appearance"), System.ComponentModel.Description("The alternating Back Color")]
			public Color AlternatingBackColor 
			{
				get 
				{
					return _alternatingBackColor;
				}
				set 
				{
					_alternatingBackColor = value;
				}
			}
			#endregion 

			#region Code readable
			/// <summary>
			/// Gets the selected items
			/// </summary>
			// Overrides the base.selectedItems to return DataRows instead ListViewItems
			[System.ComponentModel.Browsable(false)]
			public new object[] SelectedItems 
			{
				get
				{
					object[] objs = new object[base.SelectedItems.Count];
					for(int i = 0 ; i < base.SelectedItems.Count ; i++) 
						objs[i] = base.SelectedItems[i].Tag;
					return objs;
				}
			}

            public new object SelectedItemFromList
            {
                get
                {
                    return base.SelectedItems[0].Tag;
                }
            }
			/// <summary>
			/// Gets all the items in the list
			/// </summary>
			// Overrides the base.Items to return DataRows instead ListViewItems
			[System.ComponentModel.Browsable(false)]
			public new object[] Items 
			{
				get 
				{
					object[] objs = new object[base.Items.Count];
					for(int i = 0 ; i < base.Items.Count ; i++) 
						objs[i] = base.Items[i].Tag;
					return objs;
				}
			}
			/// <summary>
			/// Gets the first item that is selected
			/// </summary>
			// Adds the functionalyty to get the first selected item -- usefull in applications ;)
			[System.ComponentModel.Browsable(false)]
			public object SelectedItem 
			{
				get 
				{
					if(this.SelectedItems.Length > 0)
                        return this.SelectedItems[0];
					return null;
				}
			}
				
			#endregion

		#endregion

		#region Private functions
		private void InitViewForNull() 
		{
			if(_dataTable != null) // releases the handlers from the table
			{
				_dataTable.RowChanged -= this.onModify;
				_dataTable.RowDeleted -= this.onDelete;
			}

			base.Items.Clear();
			base.Columns.Clear();
		}
		private void InitViewForDataTable() 
		{
			_dataTable = (DataTable)_dataSource;

			InitViewFromADataTable(_dataTable);
		}
		private void InitViewForDataView() 
		{
			_dataTable = ((DataView)_dataSource).Table;

			InitViewFromADataTable(_dataTable);
		}
		private void InitViewForDataSet() 
		{
			_dataTable = ((DataSet)_dataSource).Tables[_dataMember];

			InitViewFromADataTable(_dataTable);

		}
		private void InitViewFromADataTable(DataTable table) 
		{
			if(table == null) 
			{
				if(_checkTableExists) 
					throw new NullReferenceException("The givven data member does not exists into the dataset!");
				else
					return;
			}

			// intercept any data change on the table
			table.RowChanged += onModify;
			table.RowDeleted += onDelete;

			foreach(DataColumn column in table.Columns) // create the table structure on the view
			{
				base.Columns.Add(column.Caption, 200, HorizontalAlignment.Left);
			}
			foreach(DataRow row in table.Rows) // add the rows
			{
				string[] array = new string[table.Columns.Count];
				ListViewItem item = new ListViewItem();
				item.UseItemStyleForSubItems = true;
				for(int i = 0 ; i < table.Columns.Count ; i++) 
				{
					array[i] = Convert.ToString(row[table.Columns[i]]);
					
				}
				item.Text = array[0];
				for(int i = 1 ; i < table.Columns.Count ; i++) 
				{
					item.SubItems.Add(array[i]);
				}
				item.Tag = row;
				base.Items.Add(item);
			}
		}
        private void InitViewFromAArrayList(ArrayList dataMembers)
        {
            ArrayList list = (ArrayList)this.DataSource;
            if (list == null)
            {
                this.DataSource = null;
                return;
            }
             // create the table structure on the view
            Type type;
            System.Reflection.PropertyInfo[] properties;
            if (list.Count > 0)
            {
                type = list[0].GetType();

                properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

                foreach (string[] pName in dataMembers)
                {
                    foreach (System.Reflection.PropertyInfo property in properties)
                    {
                        if (pName[0] == property.Name)
                        {
                            base.Columns.Add(pName[1], 200, HorizontalAlignment.Left);
                        }
                    }
                }


                foreach (Object obj in list) // add the rows
                {

                    ListViewItem item = new ListViewItem();
                    item.UseItemStyleForSubItems = true;
                    int count = 0;
                    foreach (string[] pName in dataMembers)
                    {
                        
                        foreach (System.Reflection.PropertyInfo property in properties)
                        {
                            if (pName[0] == property.Name)
                            {
                                if (count == 0)
                                {
                                    item.Text = Convert.ToString(property.GetValue(obj, null));
                                }
                                else
                                {
                                    item.SubItems.Add(Convert.ToString(property.GetValue(obj, null)));
                                }
                                item.Tag = obj;
                                
                                count++;
                                break;
                            }
                        }
                    }

                    base.Items.Add(item);

                }
            }
        }
		#endregion

		#region Rudimentary table style
		/// <summary>
		/// Hides a column
		/// </summary>
		/// <param name="index">column index</param>
		public void HideColumn(int index) 
		{
			base.Columns[index].Width = 0;
		}
		/// <summary>
		/// Shows a column
		/// </summary>
		/// <param name="index">column index</param>
		public void ShowColumn(int index)
		{
			base.Columns[index].Width = 200;
		}
		/// <summary>
		/// Hides a column
		/// </summary>
		/// <param name="name">column name</param>
		public void HideColumn(string name) 
		{
			foreach(ColumnHeader column in base.Columns) 
			{
				if(column.Text == name) 
				{
					column.Width = 0;
					return;
				}
			}
		}
		/// <summary>
		/// Shows a column
		/// </summary>
		/// <param name="name">column name</param>
		public void ShowColumn(string name) 
		{
			foreach(ColumnHeader column in base.Columns) 
			{
				if(column.Text == name) 
				{
					column.Width = 200;
					return;
				}
			}
		}
		/// <summary>
		/// Changes a column name
		/// </summary>
		/// <param name="index">column index</param>
		/// <param name="newName">new column name</param>
		public void SetColumnName(int index, string newName) 
		{
			base.Columns[index].Text = newName;
		}
		/// <summary>
		/// Changes a column name
		/// </summary>
		/// <param name="name">column name</param>
		/// <param name="newName">new column name</param>
		public void SetColumnName(string name, string newName) 
		{
			foreach(ColumnHeader column in base.Columns) 
			{
				if(column.Text == name) 
				{
					column.Text = newName;
					return;
				}
			}
		}
		#endregion

		#region Used for item draw
		// Structure neaded to intercept the item draw message
		private struct Item
		{
			public int left;
			public int top;
			public int right;
			public int bottom;
		}
		// Structure neaded to intercept the item draw message
		private struct DrawItemStruct 
		{
			public int ctrlType;
			public int ctrlID;
			public int itemID;
			public int itemAction;
			public int itemState;
			public IntPtr hwnd;
			public IntPtr hdc;
			public Item rcItem;
			public IntPtr itemData;
		}
		protected override void OnHandleCreated(EventArgs e)
		{
			base.OnHandleCreated(e);

			// For the control to recive the drawitem message we have to modify its style
			long lStyle = GetWindowLong(this.Handle, -16);
			lStyle |= (0x0400 | 0x0001);
			long lRet = SetWindowLong(this.Handle, -16, lStyle);
		}
		// the item drawing should be managed by someone who knows what it does and how is done, and so by inheriting :)
		protected override void WndProc(ref Message m)
		{
			base.WndProc(ref m);

			switch(m.Msg)
			{
				case (int) 0x202B : //(0x002B | 0x2000) :	// draw item
				{
					DrawItemStruct dis = (DrawItemStruct)Marshal.PtrToStructure(m.LParam,typeof(DrawItemStruct));

					//Get the graphics from the hdc field of the DrawItemStruct
					Graphics g = Graphics.FromHdc(dis.hdc);

					DrawItemState d = DrawItemState.Default;
					if((dis.itemState & 0x0001) > 0)
						d = DrawItemState.Selected;

					Rectangle r = new Rectangle(dis.rcItem.left, dis.rcItem.top, dis.rcItem.right - dis.rcItem.left, dis.rcItem.bottom - dis.rcItem.top);
					DrawItemEventArgs e = new DrawItemEventArgs(g,this.Font,r,dis.itemID,d, base.Items[dis.itemID].ForeColor, base.Items[dis.itemID].BackColor);
					// invoke the item draw method
					OnDrawItem(e);

					g.Dispose();	
					break;
				}
			}

			
		}

		public delegate void DrawItemEventHandler(object sender, DrawItemEventArgs e);
		private static readonly object DrawItemEvent = new object();
		protected virtual void OnDrawItem(DrawItemEventArgs e)
		{
			if(e.Index % 2 == 1) // item uses alternating color?
			{
				DrawItemEventArgs newDraw = new DrawItemEventArgs(e.Graphics, e.Font, e.Bounds, e.Index, e.State, e.ForeColor, _alternatingBackColor);
				e = newDraw;
			}

			e.DrawBackground();		// render background
			e.DrawFocusRectangle();	// render focus

			// format the string into the subitem rectangle
			StringFormat s = new StringFormat();
			s.FormatFlags = StringFormatFlags.NoWrap;
			s.Trimming = StringTrimming.EllipsisCharacter;
			s.Alignment = StringAlignment.Near;

			Rectangle rectCol = e.Bounds;
			ColumnHeader ch;
			// the grid columns
			for (int i=0; i < base.Columns.Count; i++)	// draw every subitem
			{
				ch = base.Columns[i];
				if(ch.Width > 0) // if column is visible draw it
                {
                    rectCol.Width = ch.Width;
                    SolidBrush brush = null;
                    if (base.SelectedItems.Contains(base.Items[e.Index]))
                        brush = new SolidBrush(Color.White);
                    else
                    {
                        if(i < base.Items[e.Index].SubItems.Count)
                        brush = new SolidBrush(base.Items[e.Index].SubItems[i].ForeColor);
                    }
                    if (i < base.Items[e.Index].SubItems.Count)
                    {
                        e.Graphics.DrawString(base.Items[e.Index].SubItems[i].Text, this.Font, brush, rectCol, s);
                    }
                    rectCol.X--; // neaded for better look
                    e.Graphics.DrawRectangle(new Pen(Color.FromName("Control")), rectCol);
                    rectCol.X++; // neaded for better look
                    rectCol.X += ch.Width;
                }
			}
			
			DrawItemEventHandler handler = (DrawItemEventHandler)Events[DrawItemEvent];
			if (handler != null)
				handler(this, e);
		}

		#endregion

		#region Data change catching
		protected void table_RowChanged(object sender, DataRowChangeEventArgs e)
		{
			if(e.Action == DataRowAction.Change) // if an item has changed
			{
				for(int i = 0 ; i < this.Items.Length ; i++) 
				{
					DataRow row = (DataRow)this.Items[i]; 

					if(row == e.Row) // find the item
					{
						ListViewItem item = base.Items[i];
						item.Text = Convert.ToString(row[0]);
						for(int j = 1 ; j < row.ItemArray.Length ; j++) // and change teh view item with the new datas
						{
							item.SubItems[j].Text = Convert.ToString(row[j]);
						}

						return;
					}
				}
			}
			else if(e.Action == DataRowAction.Add) // if an item has been added
			{
				DataTable table = _dataTable;	// add it to the base (table)

				string[] array = new string[table.Columns.Count];
				ListViewItem item = new ListViewItem();
				item.UseItemStyleForSubItems = true;
				for(int i = 0 ; i < table.Columns.Count ; i++) 
				{
					array[i] = Convert.ToString(e.Row[table.Columns[i]]);
					
				}
				item.Text = array[0];
				for(int i = 1 ; i < table.Columns.Count ; i++) 
				{
					item.SubItems.Add(array[i]);
				}
				item.Tag = e.Row;
				base.Items.Add(item);
			}
		}

		protected void table_RowDeleted(object sender, DataRowChangeEventArgs e)
		{
			for(int i = 0 ; i < this.Items.Length ; i++) 
			{
				DataRow row = (DataRow)this.Items[i];	// find the item

				if(row == e.Row) 
				{
					ListViewItem item = base.Items[i];	
					base.Items.Remove(item);	// and destroy it

					return;
				}
			}
		}
		#endregion
	}
}



